package com.team5101;

import com.alibaba.fastjson.JSON;
import com.team5101.bootstrap.ServerProvoiderBootStrap;
import com.team5101.configEntity.Constant;
import com.team5101.configEntity.DriudPoolConf;
import com.team5101.configEntity.RpcProperties;
import com.team5101.configEntity.ZooKeeperProperties;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.nio.charset.StandardCharsets;


@SpringBootApplication
public class NettyRpcProviderServerApplication implements CommandLineRunner {

    private static Logger logger = LoggerFactory.getLogger(NettyRpcProviderServerApplication.class);

    @Autowired
    private RpcProperties rpcProperties;

    @Autowired
    private CuratorFramework curatorFramework;

    @Autowired
    private ZooKeeperProperties zkProperties;

    private Boolean isCreateDataSourceConfig = false;


    @Autowired
    private ServerProvoiderBootStrap serverProvoiderBootStrap;

    public static void main(String[] args) {
        SpringApplication.run(NettyRpcProviderServerApplication.class, args);
    }


    // 实现CommandLineRunner 完成服务启动后 调用run 方法里的方法
    @Override
    public void run(String... args) throws Exception {
        serverProvoiderBootStrap.startServer();

        //连接zk服务端
        curatorFramework.start();
        curatorFramework.blockUntilConnected();
        logger.info("服务器提供端：{} ，已连接上zookeeper服务端：{}", rpcProperties.getProducerName(), zkProperties.getZkHost() + ":" + zkProperties.getPort());

        //当前的服务事务唯一名字（ZNode名称）
        String producerZnodeName = "/" + Constant.PRODUCER_TYPE + "/" + rpcProperties.getProducerName();
        String producerZnodeData = rpcProperties.getHost() + ":" + rpcProperties.getPort();

        //创建临时节点
        curatorFramework.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL)
                .forPath(producerZnodeName, producerZnodeData.getBytes(StandardCharsets.UTF_8));


        //创建一个配置中心的节点
        if (!isCreateDataSourceConfig) {
            //数据库的信息 配置中心的节点
            String zkConfigPath = "/" + Constant.ZKCONFIG + "/" + "database";
            Stat stat = curatorFramework.checkExists().forPath(zkConfigPath);
            if (null == stat) {
                DriudPoolConf driudPoolConf = new DriudPoolConf();
                driudPoolConf.setUrl("jdbc:mysql://localhost:3306/lagou?useUnicode=true&characterEncoding=UTF-8&&useSSL=false&serverTimezone=UTC&failOverReadOnly=false");
                driudPoolConf.setUsername("root");
                driudPoolConf.setPassword("123456");
                String data = JSON.toJSONString(driudPoolConf);

                curatorFramework.create()
                        .creatingParentsIfNeeded()
                        .withMode(CreateMode.PERSISTENT)
                        .forPath(zkConfigPath, data.getBytes(StandardCharsets.UTF_8));
                isCreateDataSourceConfig = true;
                logger.info(zkConfigPath + "创建成功");
            } else {
                logger.info(zkConfigPath + "已存在");
            }

        }
    }

    /**
     * 第三种监听器的添加方式: Cache 的三种实现 实践
     * Path Cache：监视一个路径下1）孩子结点的创建、2）删除，3）以及结点数据的更新。
     * 产生的事件会传递给注册的PathChildrenCacheListener。
     * Node Cache：监视一个结点的创建、更新、删除，并将结点的数据缓存在本地。
     * Tree Cache：Path Cache和Node Cache的“合体”，监视路径下的创建、更新、删除事件，并缓存路径下所有孩子结点的数据。
     * <p>
     * 能监听所有的字节点 且是无限监听的模式 但是 指定目录下节点的子节点不再监听
     */
    private static void setListeners(CuratorFramework curatorFramework, String path) throws Exception {

        //1.path Cache  连接  路径  是否获取数据
        PathChildrenCache childrenCache = new PathChildrenCache(curatorFramework, path, true);
        PathChildrenCacheListener childrenCacheListener = (client, event) -> {
            ChildData data = event.getData();
            logger.info("事件：{}", JSON.toJSONString(event));
            switch (event.getType()) {
                //添加子节点
                case CHILD_ADDED:
                    logger.info("新增子节点事件 -- 当前子节点路径：{} ，当前子节点的数据：{}", data.getPath(), new String(data.getData()));
                    break;
                //移除子节点
                case CHILD_REMOVED:
                    logger.info("移除子节点事件 -- 当前子节点路径：{} ，当前子节点的数据：{}", data.getPath(), new String(data.getData()));
                    break;
                //子节点的数据变化
                case CHILD_UPDATED:
                    logger.info("子节点数据变化事件 -- 当前子节点路径：{} ，当前子节点的数据：{}", data.getPath(), new String(data.getData()));
                    break;
                default:
                    break;
            }
        };
        childrenCache.getListenable().addListener(childrenCacheListener);
        childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
    }

}
